/**
 * 
 */
package merisis.silo;

import static java.lang.System.arraycopy;

import java.util.Arrays;

/**
 * Level-Phase-Offset Point.
 * 
 * <p> [ level ]->[ phase | offset ]->[ Object ]
 * 
 * @author Cheng-Gong Lv
 */
public class CakedCursor implements Comparable<CakedCursor> {

	private byte[] branch;

	private IndexedNode[] line;

	private byte buoy = -1;

	private SequenceSpace space;

	CakedCursor(SequenceSpace space) {

		this.space = space;
		this.branch = new byte[0];
		this.line = null;
	}

	CakedCursor(SequenceSpace space, byte[] branch) {

		this.space = space;
		this.branch = branch;
		this.line = null;
	}

	public byte level() {

		return (byte) (branch.length - 1);
	}

	public byte offset(byte phase) {

		return branch[phase];
	}

	public byte offset(int phase) {

		return offset((byte) phase);
	}

	public byte getHeadOffset() {

		return branch[level()];
	}

	public byte getLeafOffset() {

		return branch[0];
	}
	
	public CakedCursor clone() {
		
		CakedCursor inst = new CakedCursor(space);
		inst.branch = branch;
		inst.buoy = buoy;
		inst.line = line;
		
		return inst;
	}

	public void rollLevelForward() {

		byte[] newbranch = new byte[this.branch.length + 1];

		// the outermost should be reset to zero.
		arraycopy(this.branch, 0, newbranch, 0, this.branch.length - 1);

		this.branch = newbranch;
		this.line = null;
	}

	public byte getOffset(int phase) {

		return branch[phase];
	}

	public IndexedNode getRootNode() {

		final byte level = level();
		exploitTo(level);

		return line[level];
	}

	public IndexedNode getLeafNodeOfBranch() {

		return exploitToLeafNode();
	}

	public IndexedNode getNodeOfBranch(final byte phase) {

		exploitTo(phase);

		return line[phase];
	}

	public IndexedNode getDeeperNodeOfBranch(byte phase) {

		if (phase == 0)
			throw new IllegalStateException("it's leaf node");

		phase--;

		exploitTo(phase);

		return line[phase];
	}

	public void locate(byte[] pos) {

		this.line = null;
		this.branch = pos;
		resetBuoy();
	}

	public void locate(CakedCursor cur) {

		this.branch = cur.branch;
		this.line = cur.line;
		this.buoy = cur.buoy;
	}

	public CakedCursor middle(CakedCursor other) {

		byte[] middle;
		middle = CakedCursorUtil.middleOffset(branch, other.branch);

		return new CakedCursor(space, middle);
	}

	public boolean isBegin() {

		return branch != null && branch.length == 0;
	}

	public boolean isEnd() {

		return branch == null;
	}

	public boolean hasPrev() {

		if (branch == null) {
			prevEntity();
		}

		return branch.length > 0;
	}

	public void prevEntity() {

		if (branch == null) {
			branch = CakedCursorUtil.maxValueOfLevel((byte) (space.maxlevel()));
		}

		byte phase = 0;
		for (;;) {

			final byte knot = CakedCursorUtil.decrease(branch, (byte) phase);
			if (knot < branch.length) {
				raiseBuoy(knot);

			} else if (knot == branch.length) {
				// overflow of this level, go to lower level

				byte prevlevel = (byte) (branch.length - 2);
				if (prevlevel < 0) {
					branch = new byte[] {};
					return;
				}

				branch = CakedCursorUtil.maxValueOfLevel(prevlevel);
				resetBuoy();
			}

			IndexedNode n;

			n = exploitToLeafNode();
			if (n == null) {
				phase = buoy;
				continue;
			}

			if (line != null) {
				n = line[buoy];
				if (n != null) {
					if (branch[buoy] > n.getTop()) {
						branch[buoy] = n.getTop();
					}
				}
			}

			break;
		}
	}

	public boolean hasNext() {

		if (branch == null)
			return false;

		if (branch.length == 0) {

			nextEntity();
			return branch != null;
		} else {
			return true;
		}
	}

	public void nextSlot() {
		
		final byte knot = CakedCursorUtil.increase(branch, (byte) 0);
		if (knot == branch.length) {
			locate(CakedCursorUtil.minValueOfLevel(knot));
		}
	}

	public void nextEntity() {

		byte phase = 0;
		for (;;) {

			final byte knot = CakedCursorUtil.increase(branch, (byte) phase);
			if (knot < branch.length) {
				raiseBuoy(knot);

			} else if (knot == branch.length) {
				// overflow of this level, go to topper level

				locate(CakedCursorUtil.minValueOfLevel(knot));
				if (knot > space.maxlevel()) {

					// The all of nodes have been searched
					branch = null;
					return;
				}
			}

			IndexedNode n;

			if (line != null) {
				n = line[buoy];
				if (n != null) {
					if (branch[buoy] > n.getTop()) {
						phase = (byte) (buoy + 1);
						continue;
					}
				}
			}

			n = exploitToLeafNode();
			if (n == null) {
				phase = buoy;
				continue;
			}

			break;
		}
	}

	public Object get() {

		IndexedNode node = getLeafNodeOfBranch();
		if (node == null)
			return null;

		return node.get(getLeafOffset());
	}

	public String toString() {

		StringBuilder sb = new StringBuilder();
		sb.append("{");

		if (branch == null) {
			sb.append("Inf");
			sb.append("}");
			return sb.toString();
		}

		final byte level = level();
		if (level != -1) {
			sb.append(level);
			sb.append(":");
			for (int i = branch.length - 1; i > 0; i--) {
				sb.append(String.format("%02d", branch[i]));
				sb.append(",");
			}
			sb.append(String.format("%02d", branch[0]));
		}
		sb.append("}");

		return sb.toString();
	}

	@Override
	public int hashCode() {

		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(branch);
		result = prime * result + ((space == null) ? 0 : space.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {

		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final CakedCursor other = (CakedCursor) obj;
		if (!Arrays.equals(branch, other.branch))
			return false;
		if (space == null) {
			if (other.space != null)
				return false;
		} else if (!space.equals(other.space))
			return false;
		return true;
	}

	public int compareTo(CakedCursor o) {

		if (this.branch == null && o.branch == null) {
			return 0;

		} else if (this.branch == null) {
			return 1;

		} else if (this.branch == null) {
			return -1;

		}

		if (branch.length < o.branch.length) {
			return -1;

		} else if (branch.length > o.branch.length) {
			return 1;

		}

		for (byte i = (byte) (this.branch.length - 1); i >= 0; i--) {

			if (branch[i] < o.branch[i]) {
				return -1;

			} else if (branch[i] > o.branch[i]) {
				return 1;

			}
		}

		return 0;
	}

	private void raiseBuoy(byte knot) {

		if (knot > buoy)
			this.buoy = knot;
	}

	private void resetBuoy() {

		this.line = null;
		this.buoy = -1;
	}

	private IndexedNode exploitToLeafNode() {

		return exploitTo((byte) 0);
	}

	private IndexedNode exploitTo(byte knot) {

		IndexedNode n;

		do {
			n = exploitDeeperNode();
			if (n == null) {
				return null;
			}
		} while (buoy > knot);

		return n;
	}

	private IndexedNode exploitDeeperNode() {

		IndexedNode node;
		if (line == null) {

			byte level = (byte) (branch.length - 1);
			if (level < 0)
				return null;

			node = space.getRootNodeOfLevel(level);
			if (node == null) {
				buoy = (byte) (level + 1);
				return null;
			}

			line = new IndexedNode[branch.length];
			buoy = level;
			line[buoy] = node;

			return node;
		}

		node = line[buoy];
		if (node == null)
			return null;

		if (buoy == 0)
			return node;

		// exploit the deeper node
		node = (IndexedNode) node.get(branch[buoy]);
		if (node == null) {
			return null;
		}

		buoy--;
		line[buoy] = node;

		return node;
	}
}